﻿using System;
using Framework.Core.Cache;
using Xunit;

namespace Framework.Tests.Core.Cache
{
    public class HashProviderTest
    {
        private class Toto
        {
            public string Name { get; set; }

            private bool Equals(Toto other)
            {
                return string.Equals(Name, other.Name);
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj))
                {
                    return false;
                }
                if (ReferenceEquals(this, obj))
                {
                    return true;
                }
                if (obj.GetType() != GetType())
                {
                    return false;
                }
                return Equals((Toto) obj);
            }

            public override int GetHashCode()
            {
                return (Name != null ? Name.GetHashCode() : 0);
            }
        }

        [Serializable]
        private class BadToto
        {
            public string Name { get; set; }
        }

        private readonly IHashProvider target;

        public HashProviderTest()
        {
            target = new HashProviderForLegacy();
        }

        [Fact]
        public void Should_return_hash_code_for_value_type()
        {
            TestHashcodeBase(true, true.GetHashCode());
        }

        [Fact]
        public void Should_return_hash_code_for_string()
        {
            TestHashcodeBase("pouet", "pouet".GetHashCode());
        }

        [Fact]
        public void Should_return_hash_code_for_entity_that_implement_Hashcode()
        {
            object o = new Toto {Name = "pouet"};
            TestHashcodeBase(o, o.GetHashCode());
        }

        [Fact]
        public void Hashcode_for_dirty_equal()
        {
            Assert.Equal(target.Create(new BadToto {Name = "pouet"}), target.Create(new BadToto {Name = "pouet"}));
        }

        [Fact]
        public void Hashcode_for_dirty_not_equal()
        {
            var expected = target.Create(new BadToto {Name = "toto"});
            var actual = target.Create(new BadToto {Name = "pouet"});
            Assert.NotEqual(expected, actual);
        }

        private void TestHashcodeBase(object o, int expected)
        {
            var actual = target.Create(o);
            Assert.Equal(expected, actual);
        }

        [Serializable]
        public class EntityWhithoutHascodeOverride
        {
            public EntityWhithoutHascodeOverride(string name, int id)
            {
                Name = name;
                Id = id;
            }

            public string Name { get; private set; }

            public int Id { get; private set; }
        }

        [Fact]
        public void Should_use_legacy_hascode()
        {
            var e1 = new EntityWhithoutHascodeOverride("pouet", 1);
            var e2 = new EntityWhithoutHascodeOverride("pouet", 1);

            Assert.Equal(target.Create(e1), target.Create(e2));
        }

        public class EntityWithWhellHascodeOverride : IEquatable<EntityWithWhellHascodeOverride>
        {
            public EntityWithWhellHascodeOverride(string name, int id)
            {
                Name = name;
                Id = id;
            }

            public string Name { get; private set; }

            public int Id { get; private set; }

            public bool Equals(EntityWithWhellHascodeOverride other)
            {
                return string.Equals(Name, other.Name) && Id == other.Id;
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj))
                {
                    return false;
                }
                if (ReferenceEquals(this, obj))
                {
                    return true;
                }
                if (obj.GetType() != GetType())
                {
                    return false;
                }
                return Equals((EntityWithWhellHascodeOverride)obj);
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Id;
                }
            }
        }

        [Fact]
        public void Should_use_hascode_overriden()
        {
            var e1 = new EntityWithWhellHascodeOverride("pouet", 1);
            var e2 = new EntityWithWhellHascodeOverride("pouet", 1);

            Assert.Equal(target.Create(e1), target.Create(e2));
        }

        [Fact]
        public void Should_return_null_if_object_is_null()
        {
            Assert.Equal(0, target.Create(null));
        }
    }
}